package mybatispuls;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import mybatis.annotation.EntityTypeAlias;
import mybatis.exceptions.UnsafeFieldException;
import mybatis.interfaces.*;
import org.apache.commons.lang3.StringUtils;
import org.reflections.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Timestamp;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


/**
 * @author CKC
 */
@Slf4j
public class QueryAdapter<T> {

    protected static final String DESC_FLAG = "-";
    protected static final Pattern SAFE_FIELD_PATTERN = Pattern.compile("[a-zA-Z0-9_-]+");
    /**
     * 缓存类的的field解析，减少不必要的CPU开支
     */
    protected static final Map<Class<?>, Map<String, Field>> ENTITY_FIELDS = new ConcurrentHashMap<>();
    protected final Object query;
    protected final Class<T> entityType;


    public QueryAdapter(Object query, Class<T> entityType) {
        this.query = query;
        this.entityType = entityType;
    }

    /**
     * 数据库特殊字符转义
     *
     * @param value 原值
     * @return 新值
     */
    public static String convertValue(String value) {
        if (StringUtils.isNotEmpty(value)) {
            value = value.replace("\\\\", "\\\\\\\\");
            value = value.replace("_", "\\_");
            value = value.replace("%", "\\%");
        }
        return value;
    }

    public QueryWrapper<T> buildQueryWrapper() {
        return buildQueryWrapper(null);
    }

    public QueryWrapper<T> buildQueryWrapper(QueryWrapper<T> queryWrapper) {

        if (queryWrapper == null) {
            queryWrapper = new QueryWrapper<>();
        }

        String type = Optional.ofNullable(entityType.getAnnotation(EntityTypeAlias.class)).map(EntityTypeAlias::value).orElse(null);
        if (type != null && query instanceof IFieldQueryCondition) {
            List<String> select = ((IFieldQueryCondition) query).getFields(type);
            assertSafeFields(select);
            convertSelectedField(select);
            if (select != null && !select.isEmpty()) {
                queryWrapper.select(select.toArray(new String[0]));
            }
        }

        if (query instanceof IFilterQueryCondition) {
            IFilterQueryCondition filter = (IFilterQueryCondition) query;
            if (!filter.isEmptyFilter()) {
                queryWrapper.and(wrapper -> {
                    filter.getFilter().forEach((f, v) -> {
                        assertSafeField(f);
                        if (v.size() > 1) {
                            wrapper.in(convertConditionField(f), v);
                        } else if (v.size() == 1) {
                            wrapper.eq(convertConditionField(f), convertConditionValue(f, v.get(0)));
                        } else {
                            wrapper.isNull(convertConditionField(f));
                        }
                    });
                    //return wrapper;
                });
            }
        }
        queryWrapper.and((wrapper) -> wrapper.eq("ss", 123));

        if (query instanceof IFilterOrQueryCondition) {
            IFilterOrQueryCondition filterOr = (IFilterOrQueryCondition) query;
            if (!filterOr.isEmptyFilterOr()) {
                queryWrapper.and(wrapper -> {
                    filterOr.getFilterOr().forEach((f, v) -> {
                        assertSafeField(f);
                        if (v.size() > 1) {
                            wrapper.or(wrapper1 -> wrapper1.in(convertConditionField(f), v));
                        } else if (v.size() == 1) {
                            wrapper.or(wrapper1 -> wrapper1.eq(convertConditionField(f), convertConditionValue(f, v.get(0))));
                        } else {
                            wrapper.or(wrapper1 -> wrapper1.isNull(convertConditionField(f)));
                        }
                    });
                    // return wrapper;
                });
            }
        }

        if (query instanceof INotEqualsQueryCondition) {
            INotEqualsQueryCondition notEquals = (INotEqualsQueryCondition) query;
            if (!notEquals.isEmptyNeq()) {
                queryWrapper.and(wrapper -> {
                    notEquals.getNeq().forEach((f, v) -> {
                        assertSafeField(f);
                        if (v.size() > 1) {
                            wrapper.notIn(convertConditionField(f), v);
                        } else if (v.size() == 1) {
                            wrapper.ne(convertConditionField(f), convertConditionValue(f, v.get(0)));
                        } else {
                            wrapper.isNotNull(convertConditionField(f));
                        }
                    });
                    // return wrapper;
                });
            }
        }

        if (query instanceof ILessThanQueryCondition) {
            ILessThanQueryCondition lessThan = (ILessThanQueryCondition) query;
            if (!lessThan.isEmptyLt()) {
                queryWrapper.and(wrapper -> {
                    lessThan.getLt().forEach((f, v) -> {
                        assertSafeField(f);
                        if (v != null) {
                            wrapper.lt(convertConditionField(f), convertConditionValue(f, v));
                        }
                    });
                    // return wrapper;
                });
            }
        }

        if (query instanceof ILessThanOrEqualsQueryCondition) {
            ILessThanOrEqualsQueryCondition lessThanOrEquals = (ILessThanOrEqualsQueryCondition) query;
            if (!lessThanOrEquals.isEmptyLte()) {
                queryWrapper.and(wrapper -> {
                    lessThanOrEquals.getLte().forEach((f, v) -> {
                        assertSafeField(f);
                        if (v != null) {
                            wrapper.le(convertConditionField(f), convertConditionValue(f, v));
                        }
                    });
                    // return wrapper;
                });
            }
        }

        if (query instanceof IGreaterThanQueryCondition) {
            IGreaterThanQueryCondition greaterThan = (IGreaterThanQueryCondition) query;
            if (!greaterThan.isEmptyGt()) {
                queryWrapper.and(wrapper -> {
                    greaterThan.getGt().forEach((f, v) -> {
                        assertSafeField(f);
                        if (v != null) {
                            wrapper.gt(convertConditionField(f), convertConditionValue(f, v));
                        }
                    });
                    // return wrapper;
                });
            }
        }
        if (query instanceof IGreaterThanOrEqualsQueryCondition) {
            IGreaterThanOrEqualsQueryCondition greaterThanOrEquals = (IGreaterThanOrEqualsQueryCondition) query;
            if (!greaterThanOrEquals.isEmptyGte()) {
                queryWrapper.and(wrapper -> {
                    greaterThanOrEquals.getGte().forEach((f, v) -> {
                        assertSafeField(f);
                        if (v != null) {
                            wrapper.ge(convertConditionField(f), convertConditionValue(f, v));
                        }
                    });
                    // return wrapper;
                });
            }
        }

        if (query instanceof ISearchQueryCondition) {
            ISearchQueryCondition search = (ISearchQueryCondition) query;
            if (!search.isEmptySearch()) {
                queryWrapper.and(wrapper -> {
                    search.getSearch().forEach((f, v) -> {
                        assertSafeField(f);
                        if (v != null) {
                            wrapper.like(convertConditionField(f), convertValue(convertConditionValue(f, v)));
                        }
                    });
                    // return wrapper;
                });
            }
        }


        if (query instanceof ISearchOrQueryCondition) {
            ISearchOrQueryCondition searchOr = (ISearchOrQueryCondition) query;
            if (!searchOr.isEmptySearchOr()) {
                queryWrapper.and(wrapper -> {
                    searchOr.getSearchOr().forEach((f, v) -> {
                        assertSafeField(f);
                        if (v != null) {
                            wrapper.or(wrapper1 -> wrapper1.like(convertConditionField(f), convertValue(convertConditionValue(f, v))));
                        }
                    });
                    // return wrapper;
                });
            }
        }

        if (query instanceof ISearchLeftQueryCondition) {
            ISearchLeftQueryCondition searchLeft = (ISearchLeftQueryCondition) query;
            if (!searchLeft.isEmptySearchL()) {
                queryWrapper.and(wrapper -> {
                    searchLeft.getSearchL().forEach((f, v) -> {
                        assertSafeField(f);
                        if (v != null) {
                            wrapper.likeLeft(convertConditionField(f), convertValue(convertConditionValue(f, v)));
                        }
                    });
                    //return wrapper;
                });
            }
        }
        if (query instanceof ISearchRightQueryCondition) {
            ISearchRightQueryCondition searchRight = (ISearchRightQueryCondition) query;
            if (!searchRight.isEmptySearchR()) {
                queryWrapper.and(wrapper -> {
                    searchRight.getSearchR().forEach((f, v) -> {
                        assertSafeField(f);
                        if (v != null) {
                            wrapper.likeRight(convertConditionField(f), convertValue(convertConditionValue(f, v)));
                        }
                    });
                    // return wrapper;
                });
            }
        }
        return queryWrapper;
    }

    public IPage<T> buildPage() {
        return buildPage(null);
    }

    public IPage<T> buildPage(Page<T> page) {
        if (page == null) {
            page = new Page<>();
        }
        if (query instanceof IPageQueryCondition) {
            int current = ((IPageQueryCondition) query).getPageCurrent();
            int size = ((IPageQueryCondition) query).getPageSize();
            page.setCurrent(current);
            page.setSize(size);
        }

        if (query instanceof ISortQueryCondition) {
            List<String> descField = new ArrayList<>();
            List<String> ascField = new ArrayList<>();
            Optional.ofNullable(((ISortQueryCondition) query).getSort()).ifPresent(map -> map.forEach(f -> {
                if (f != null) {
                    if (f.startsWith(DESC_FLAG)) {
                        descField.add(f.substring(1));
                    } else {
                        ascField.add(f);
                    }
                }
            }));
            assertSafeFields(ascField);
            if (!ascField.isEmpty()) {
                convertConditionField(ascField);
                //page.setAscs(ascField);
            }
            assertSafeFields(descField);
            if (!descField.isEmpty()) {
                convertConditionField(descField);
                //page.setDescs(descField);
            }
        }

        return page;
    }

    /**
     * 本来想做成过滤，但是过滤的话会影响 isEmptyxxx 的判断，所以就直接抛异常
     *
     * @param field
     */
    protected void assertSafeField(String field) {
        if (!SAFE_FIELD_PATTERN.matcher(field).matches()) {
            throw new UnsafeFieldException("unsafe field: " + field);
        }
    }

    private void assertSafeFields(List<String> fields) {
        if (fields == null) {
            return;
        }
        fields.forEach(this::assertSafeField);
    }

    @SuppressWarnings("unchecked")
    protected Map<String, Field> getFields() {
        if (entityType == null) {
            return null;
        }
        Map<String, Field> fields = ENTITY_FIELDS.get(entityType);
        if (fields == null) {
            fields = ReflectionUtils.getAllFields(entityType,
                            f -> f != null && !Modifier.isStatic(f.getModifiers()) && !Modifier.isFinal(f.getModifiers()))
                    .stream()
                    .collect(Collectors.toMap(Field::getName, f -> f));
            ENTITY_FIELDS.put(entityType, fields);
        }
        return fields;
    }

    protected Field getField(String field) {
        Map<String, Field> fields = getFields();
        if (fields == null) {
            return null;
        }
        return fields.get(field);
    }

    private void convertSelectedField(List<String> select) {
        if (select == null) {
            return;
        }
        for (int i = 0; i < select.size(); i++) {
            select.set(i, convertConditionField(select.get(i)));
        }
    }

    private void convertConditionField(List<String> fields) {
        if (fields == null) {
            return;
        }
        for (int i = 0; i < fields.size(); i++) {
            fields.set(i, convertConditionField(fields.get(i)));
        }
    }

    protected String convertConditionField(String field) {
        return field;
    }

    protected Object convertConditionValue(String field, Object value) {
        if (value == null) {
            return null;
        }
        Field f = getField(field);
        if (f == null) {
            return value;
        }
        if (Date.class.isAssignableFrom(f.getType())) {
            if (value instanceof Date) {
                return new Timestamp(((Date) value).getTime());
            } else if (value instanceof Long) {
                // 目前只支持精确到毫秒的时间戳
                return new Timestamp((Long) value);
            } else {
                // 目前只支持精确到毫秒的时间戳
                return new Timestamp(Long.parseLong(String.valueOf(value)));
            }
        } else {
            return value;
        }
    }

    protected String convertConditionValue(String field, String value) {
        return value;
    }
}
